package show.me.the.money.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.springframework.beans.BeansException;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import show.me.the.money.accredit.User;
import show.me.the.money.accredit.UsersController;
import show.me.the.money.accredit.result.AuthorizeResult;
import show.me.the.money.service.core.ExecuteResult;
import show.me.the.money.service.core.Service;
import show.me.the.money.service.core.ServiceExecutor;
import show.me.the.money.util.collect.OnlineUserInfo;
import show.me.the.money.util.collect.ServiceInfoCollector;
import show.me.the.money.util.context.Context;
import show.me.the.money.util.context.UserContext;


@SuppressWarnings("unchecked")
public class Dispatcher extends HttpServlet {
	
	private static final long serialVersionUID = -8260076694457868119L;
	private static Log logger = LogFactory.getLog(Dispatcher.class);
	private static WebApplicationContext wac;
	private static ObjectMapper jsonMapper =  new ObjectMapper();
	private static final long SERVICE_INVOKE_TIMECOST_WARNNING_LEVEL = 3000;
	
	public void init(ServletConfig cfg) throws ServletException {
		wac = WebApplicationContextUtils.getRequiredWebApplicationContext(cfg.getServletContext());
		logger.info("inited...");
		jsonMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);
		jsonMapper.getSerializationConfig().setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
	}
	
	public void doPost(HttpServletRequest request, HttpServletResponse response){
		if(request.getContentType().indexOf("application/json") == -1){
			setResponseHeader(response,501,"UnknowRequest");
			return;
		}
		int code = 200;
		String msg = "Success";
		Date startDt = new Date();
		String serviceId = "";
		HashMap<String,Object> req = null;
		try {
			req = parseRequest(request);
			HashMap<String,Object> res = new HashMap<String,Object>();
			if(!req.containsKey("serviceId")){
				setResInfo(res, 401, "MissingServiceId");
			}
			else{
				serviceId = (String)req.get("serviceId");
				res.put("serviceId",serviceId);
				setResInfo(res, code, msg);
				User user = getUser(request);
				if(user != null){
					AuthorizeResult r = user.authorize("service", serviceId);
					if(r != null && r.getResult() == AuthorizeResult.YES){
						Context ctx = createContext(request);
						Context userCtx = new UserContext(user);
						ctx.putCtx("user", userCtx);
						dispatch(req,res,ctx);
					}
					else{
						clearUser(request);
						setResInfo(res, 402, "AuthorizeFailed");
					}
				}
				else{
					if(serviceId.equals("logon") || serviceId.equals("roleLocator")){
						Context ctx = createContext(request);
						dispatch(req,res,ctx);
					}
					else{
						setResInfo(res, 403, "NotLogon");
					}
				}
			}
			writeToResponse(response,res);
		}
		catch (UnsupportedEncodingException e) {
			msg = "UnsupportedEncodingException:"+ e.getMessage();
			code = 501;
		} 
		catch (IOException e) {
			msg = "IOException:"+ e.getMessage();
			code = 502;
		} 
		catch(Exception e){
			msg = "UnKnowException:" + e.getMessage();
			code = 500;
		}
		finally{
			Date endDt = new Date();
			long timeCost = endDt.getTime() - startDt.getTime();
			ServiceInfoCollector.instance().add(serviceId, timeCost, startDt);
			String reqStr = req != null ? req.toString() : "";
			if(timeCost >= SERVICE_INVOKE_TIMECOST_WARNNING_LEVEL){
				logger.warn("service[" + serviceId + "] call time is too long:" + timeCost + "\r\nrequestJSON:" +reqStr);
			}
		}
		if(code > 400){
			logger.error(msg);
		}
		setResponseHeader(response,code,msg);
	}
	
	private void dispatch(HashMap<String,Object> req,HashMap<String,Object> res,Context ctx){
		int code = 200;
		String msg = "Success";
		try{
			String id = (String)req.get("serviceId");
			ExecuteResult r = ServiceExecutor.execute(id, req, res, ctx);
			code = r.getCode();
			msg = r.getMsg();
		}
		catch(BeansException e){
			code = 404;
			msg = "LookupServiceBeanFailed";
		}
		catch(ClassCastException e){
			code = 405;
			msg = "ServiceCastFailed";
		}
		catch(Exception e){
			logger.error(e.getMessage(),e);
			code = 500;
			msg = "ServiceExecuteFailed";
		}
		setResInfo(res, code, msg);
	}
	
	private void setResponseHeader(HttpServletResponse response,int code,String msg){
		response.setHeader(Service.RES_CODE, String.valueOf(code));
		response.setHeader(Service.RES_MESSAGE, msg);
	}

	public static void clearUser(HttpServletRequest request){
		request.getSession().removeAttribute("uid");
	}	
	
	public static User getUser(HttpServletRequest request){
		String id = (String)request.getSession().getAttribute("uid");
		if(id == null){
			return null;
		}
		return UsersController.instance().getUser(id);
	}
	
	public static Context createContext(HttpServletRequest request){
		Context ctx = new Context();
		ctx.put(Context.APP_CONTEXT, wac);
		HttpSession session = request.getSession();
//		session.setAttribute(OnlineUserInfo.SERVER_IP, request.getLocalAddr());
		session.setAttribute(OnlineUserInfo.SERVER_IP, SystemInitialize.SERVER_IP);
		session.setAttribute(OnlineUserInfo.SERVER_PORT, String.valueOf(request.getLocalPort()));
		session.setAttribute(OnlineUserInfo.CLIENT_IP, request.getRemoteAddr());
		session.setAttribute(OnlineUserInfo.USER_AGENT, request.getHeader("user-agent"));
		ctx.put(Context.WEB_SESSION, session);
//		ctx.put(Context.HTTP_REQUEST, request);
		return ctx;
	}
	
	
	public static HashMap<String,Object> parseRequest(HttpServletRequest request){
		try{
			String encoding = request.getCharacterEncoding();
			if(encoding == null){
				encoding = "UTF-8";
			}
			InputStream ins = request.getInputStream();
			return jsonMapper.readValue(ins, HashMap.class);
		}
		catch(Exception e){
			logger.error("parseRequest failed:", e);
		}
		return new HashMap<String,Object>();
	}
	
	public static void writeToResponse(HttpServletResponse response,HashMap<String,Object> res) throws IOException{
		response.addHeader("content-type","text/javascript");
		OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream(),"utf-8");
		jsonMapper.writeValue(out, res);
	}
	
	public static void setResInfo(HashMap<String, Object> res, int code, String msg){
		res.put(Service.RES_CODE, code);
		res.put(Service.RES_MESSAGE, msg);
	}
}
